package services;

import java.util.List;

import javax.persistence.Query;

import play.db.jpa.JPA;

public class DAO {
	/**
	 * Permite buscar todas las entidades de tipo <code>clazz</code> tales que
	 * el campo <code>field</code> es igual a <code>value</code>
	 * 
	 * @param clazz
	 * @param field
	 * @param value
	 * @return Una lista o un elemento según se haya especificado. Si Es una
	 *         lista puede estar vacía. Si es un elemento puede retornar
	 *         <code>null</code>
	 */
	public static <T> T query(Class<?> clazz, int numResults, Object... params) {
		String s = String.format("SELECT x FROM %s x WHERE 1=1",
				clazz.getSimpleName());

		String p = "";
		for (int i = 0; i < params.length; i += 2)
			p += " AND x." + params[i] + "=:" + params[i];
		s += p;
		return execute(s, numResults, params);
	}

	public static <T> T queryLike(Class<?> clazz, int numResults,
			Object... params) {
		String s = String.format("SELECT x FROM %s x WHERE 1=1",
				clazz.getSimpleName());

		String p = "";
		for (int i = 0; i < params.length; i += 2)
			p += " AND x." + params[i] + " LIKE :" + params[i] + "";
		s += p;
		return execute(s, numResults, params);
	}

	public static <C, K> C find(Class<C> c, K id) {
		return JPA.em().find(c, id);
	}

	public static <T> Object navigate(Class<T> clazz, Long id,
			String nameOfAttribute) {
		String s = String.format("SELECT x.%s FROM %s x WHERE x.id = :id",
				nameOfAttribute, clazz.getSimpleName());

		return execute(s, -1, "id", id);
	}

	public static <T> void persist(T o) {
		JPA.em().persist(o);
	}

	/**
	 * Ejecuta el query con los parametros especificados. Solo se retorna el
	 * número de resultados indicado
	 * 
	 * @param query
	 *            Query
	 * @param numResults
	 *            Número de resultados esperado
	 *            <ul>
	 *            <li><strong>-1</strong>: Los que retorne la consuta, Retorna
	 *            una lista</li>
	 *            <li><strong>1</strong>:El primero. Retorna un elemento NO una
	 *            lista</li>
	 *            <li><strong>N</strong>:El número indicado. Retorna una lista</li>
	 *            </ul>
	 * @param params
	 *            Parametros a pasar a la consulta. Deben estar de a pares,
	 *            parametro y valor
	 * @return Una lista o un elemento según se haya especificado. Si Es una
	 *         lista puede estar vacía. Si es un elemento puede retornar
	 *         <code>null</code>
	 */
	public static <T> T execute(String query, int numResults, Object... params) {
		Query q = JPA.em().createQuery(query);
		return execute(q, numResults, params);
	}

	/**
	 * Ejecuta el query con los parametros especificados. Solo se retorna el
	 * número de resultados indicado
	 * 
	 * @param query
	 *            Nombre del query
	 * @param numResults
	 *            Número de resultados esperado
	 *            <ul>
	 *            <li><strong>-1</strong>: Los que retorne la consuta, Retorna
	 *            una lista</li>
	 *            <li><strong>1</strong>:El primero. Retorna un elemento NO una
	 *            lista</li>
	 *            <li><strong>N</strong>:El número indicado. Retorna una lista</li>
	 *            </ul>
	 * @param params
	 *            Parametros a pasar a la consulta. Deben estar de a pares,
	 *            parametro y valor
	 * @return Una lista o un elemento según se haya especificado. Si Es una
	 *         lista puede estar vacía. Si es un elemento puede retornar
	 *         <code>null</code>
	 */
	public static <T> T executeNamed(String query, int numResults,
			Object... params) {
		Query q = JPA.em().createNamedQuery(query);

		return execute(q, numResults, params);
	}

	/**
	 * Ejecuta el query con los parametros especificados. Solo se retorna el
	 * número de resultados indicado
	 * 
	 * @param q
	 *            Query
	 * @param numResults
	 *            Número de resultados esperado
	 *            <ul>
	 *            <li><strong>-1</strong>: Los que retorne la consuta, Retorna
	 *            una lista</li>
	 *            <li><strong>1</strong>:El primero. Retorna un elemento NO una
	 *            lista</li>
	 *            <li><strong>N</strong>:El número indicado. Retorna una lista</li>
	 *            </ul>
	 * @param params
	 *            Parametros a pasar a la consulta. Deben estar de a pares,
	 *            parametro y valor
	 * @return Una lista o un elemento según se haya especificado. Si Es una
	 *         lista puede estar vacía. Si es un elemento puede retornar
	 *         <code>null</code>
	 */
	@SuppressWarnings("unchecked")
	private static <T> T execute(Query q, int numResults, Object... params) {
		if (params.length % 2 != 0) {
			String s = stringParams(params);
			throw new IllegalArgumentException(
					"Número incorrecto de parametros: (" + params.length + ")"
							+ s);
		}
		try {
			for (int i = 0; i < params.length; i += 2)
				q.setParameter(params[i].toString(), params[i + 1]);
			if (numResults > 0)
				q.setMaxResults(numResults);

			@SuppressWarnings("rawtypes")
			List res = q.getResultList();
			if (numResults == 1)
				return (T) (res.isEmpty() ? null : res.get(0));
			return (T) res;
		} catch (Exception e) {
			System.out.println("Error al ejecutar una consulta ");
			System.out.println("Número de resultados: " + numResults);
			System.out.println("Parámetros (" + params.length + "): "
					+ stringParams(params));

			throw new RuntimeException(e);
		}
	}

	private static String stringParams(Object... params) {
		String s = "[";
		for (Object o : params)
			s += o.toString() + "(" + o.getClass() + "), ";
		s = s.substring(0, s.length() - 2) + "]";
		return s;
	}

	public static <T> void merge(T c) {
		JPA.em().merge(c);
	}

	public static void flush() {
		JPA.em().flush();
	}

	public static <T> void remove(T c) {
		JPA.em().remove(c);
	}

	public static <T> List<T> list(Class<T> clazz) {
		String s = String.format("SELECT x FROM %s x", clazz.getSimpleName());
		return DAO.execute(s, -1);
	}

	public static <T> void detach(T c) {
		JPA.em().detach(c);

	}

	public static <T> T executeNative(String query, int numResults,
			Object... params) {
		Query q = JPA.em().createNativeQuery(query);
		return execute(q, numResults, params);
	}
}
